#include <common.h>
#include <dm.h>
#include <i2c.h>
#include <pci.h>
#include <asm/io.h>
#include <asm/arch/hardware.h>
#include <fh_device.h>
#include "fh_i2c.h"

/*
 * i2c_set_bus_speed - Set the i2c speed
 * @speed:	required i2c speed
 *
 * Set the i2c speed.
 */
static unsigned int __fh_i2c_set_bus_speed(unsigned int i2c_base,
					   unsigned int speed)
{
	unsigned int ic_con;
	int i2c_spd;

	if (speed < I2C_SPEED_FAST)
		i2c_spd = IC_SPEED_MODE_STANDARD;
	else
		i2c_spd = IC_SPEED_MODE_FAST;

	I2c_DisEnable(i2c_base);
	ic_con = I2c_GetCon(i2c_base);
	switch (i2c_spd) {
	case IC_SPEED_MODE_STANDARD:
		ic_con &= ~(1 << 2);
		ic_con |= FH_IC_CON_SPEED_STD;
		break;
	case IC_SPEED_MODE_FAST:
	default:
		ic_con &= ~(1 << 1);
		ic_con |= FH_IC_CON_SPEED_FAST;
		break;
	}
	I2c_SetCon(i2c_base, ic_con);
	return 0;
}

/*
 * i2c_flush_rxfifo - Flushes the i2c RX FIFO
 *
 * Flushes the i2c RX FIFO
 */
static void i2c_flush_rxfifo(unsigned int i2c_base)
{
	while (I2c_Status(i2c_base) & IC_STATUS_RFNE)
		readl(i2c_base + FH_IC_DATA_CMD);
}

/*
 * i2c_wait_for_bb - Waits for bus busy
 *
 * Waits for bus busy
 */
static int i2c_wait_for_bb(unsigned int i2c_base)
{
	unsigned long start_time_bb = get_timer(0);

	while ((I2c_Status(i2c_base) & IC_STATUS_MA)) {

		/* Evaluate timeout */
		if (get_timer(start_time_bb) > I2C_WAIT_TIME)
			return 1;
	}

	return 0;
}

static int i2c_xfer_init(unsigned int i2c_base, uchar chip, uint addr,
			 int alen)
{	u32 stat = 0;

	if (i2c_wait_for_bb(i2c_base))
		return 1;
	I2c_DisEnable(i2c_base);
	I2c_SetDeviceId(i2c_base, chip);
	I2c_Enable(i2c_base);
	I2c_SetIntrMask(i2c_base, FH_IC_INTR_DEFAULT_MASK);
	while (!((stat & FH_IC_INTR_TX_ABRT) || (stat & FH_IC_INTR_TX_EMPTY))) {
		stat = I2C_GetIntrStat(i2c_base);
	}
	if (stat & FH_IC_INTR_TX_ABRT) {
		printf("FH_IC_INTR_TX_ABRT\n");
		I2c_ClrIntr(i2c_base, FH_IC_CLR_TX_ABRT);
		return -1;
	}
	while (alen) {
		alen--;
		/* high byte address going out first */
		I2c_Write(i2c_base,
				(addr >> (alen * 8)) & 0xff);
	}
	return 0;
}

static int i2c_xfer_finish(unsigned int i2c_base)
{
	ulong start_stop_det = get_timer(0);

	while (1) {
		if ((readl(i2c_base + FH_IC_RAW_INTR_STAT) & IC_STOP_DET)) {
			readl(i2c_base + FH_IC_CLR_STOP_DET);
			break;
		} else if (get_timer(start_stop_det) > I2C_WAIT_TIME) {
			break;
		}
	}

	if (i2c_wait_for_bb(i2c_base)) {
		printf("Timed out waiting for bus\n");
		return 1;
	}

	i2c_flush_rxfifo(i2c_base);
	I2c_SetIntrMask(i2c_base, FH_IC_INTR_NONE);

	return 0;
}

/*
 * i2c_read - Read from i2c memory
 * @chip:	target i2c address
 * @addr:	address to read from
 * @alen:
 * @buffer:	buffer for read data
 * @len:	no of bytes to be read
 *
 * Read from i2c memory.
 */
static int __fh_i2c_read(unsigned int i2c_base, u8 dev, uint addr,
			 int alen, u8 *buffer, int len)
{
	unsigned long start_time_rx;
	unsigned int active = 0;

	if (i2c_xfer_init(i2c_base, dev, addr, alen))
		return 1;
	start_time_rx = get_timer(0);
	while (len) {
		if (!active) {
			/*
			 * Avoid writing to ic_cmd_data multiple times
			 * in case this loop spins too quickly and the
			 * ic_status RFNE bit isn't set after the first
			 * write. Subsequent writes to ic_cmd_data can
			 * trigger spurious i2c transfer.
			 */
			while (I2C_GetTransmitFifoLevel(i2c_base) &&
						(get_timer(start_time_rx) <= I2C_WAIT_TIME))
						;
			if (len == 1)
				I2c_Write(i2c_base, IC_CMD | IC_STOP);
			else
				I2c_Write(i2c_base, IC_CMD);
			active = 1;
		}

		if (I2c_Status(i2c_base) & IC_STATUS_RFNE) {
			*buffer++ = (uchar)I2c_Read(i2c_base);
			len--;
			start_time_rx = get_timer(0);
			active = 0;
		}
		else if (get_timer(start_time_rx) > I2C_WAIT_TIME) {
			return 1;
		}
	}

	return i2c_xfer_finish(i2c_base);
}

/*
 * i2c_write - Write to i2c memory
 * @chip:	target i2c address
 * @addr:	address to read from
 * @alen:
 * @buffer:	buffer for read data
 * @len:	no of bytes to be read
 *
 * Write to i2c memory.
 */
static int __fh_i2c_write(unsigned int i2c_base, u8 dev, uint addr,
			  int alen, u8 *buffer, int len)
{
	int nb = len;
	unsigned long start_time_tx;

	if (i2c_xfer_init(i2c_base, dev, addr, alen))
		return 1;

	start_time_tx = get_timer(0);
	while (len) {
		if (I2c_Status(i2c_base) & IC_STATUS_TFNF) {
			while (I2C_GetTransmitFifoLevel(i2c_base) &&
						(get_timer(start_time_tx) <= I2C_WAIT_TIME * nb))
						;
			if (--len == 0) {
				I2c_Write(i2c_base, *buffer | IC_STOP);
			} else {
				I2c_Write(i2c_base, *buffer);
			}
			buffer++;
			start_time_tx = get_timer(0);

		} else if (get_timer(start_time_tx) > (nb * I2C_WAIT_TIME)) {
				printf("Timed out. i2c write Failed\n");
				return 1;
		}
	}

	return i2c_xfer_finish(i2c_base);
}

static u32 i2c_fh_scl_hcnt(u32 ic_clk, u32 tSYMBOL,
							u32 tf, int cond, int offset)
{
	u32 ret = 0;

	if (cond)
		ret = (ic_clk * tSYMBOL + 5000) / 10000 - 8 + offset;
	else
		ret = (ic_clk * (tSYMBOL + tf) + 5000) / 10000 - 3 + offset;
	return ret;
}

static u32 i2c_fh_scl_lcnt(u32 ic_clk, u32 tLOW, u32 tf, int offset)
{
	return ((ic_clk * (tLOW + tf) + 5000) / 10000) - 1 + offset;
}

static void __fh_i2c_init(unsigned int i2c_base, unsigned int speed, int slaveaddr)
{
	u32 hcnt, lcnt;
	u32 input_clock_khz = IC_CLK / 1000;

	/* Disable i2c */
	I2c_DisEnable(i2c_base);

	I2c_SetCon(i2c_base, (IC_CON_SD | IC_CON_SPD_FS | IC_CON_MM));
	I2c_SetTxRxTl(i2c_base, IC_TX_TL, IC_RX_TL);
	I2c_SetIntrMask(i2c_base, IC_STOP_DET);
	writel(slaveaddr, i2c_base + FH_IC_SAR);

	/* Standard-mode */
	hcnt = i2c_fh_scl_hcnt(input_clock_khz,
				40,	/* tHD;STA = tHIGH = 4.0 us */
				3,	/* tf = 0.3 us */
				0,	/* 0: DW default, 1: Ideal */
				0);	/* No offset */
	lcnt = i2c_fh_scl_lcnt(input_clock_khz,
				47,	/* tLOW = 4.7 us */
				3,	/* tf = 0.3 us */
				0);	/* No offset */
	I2c_SetSsHcnt(i2c_base, hcnt);
	I2c_SetSsLcnt(i2c_base, lcnt);
	/* Fast-mode */
	hcnt = i2c_fh_scl_hcnt(input_clock_khz,
				6,	/* tHD;STA = tHIGH = 0.6 us */
				3,	/* tf = 0.3 us */
				0,	/* 0: DW default, 1: Ideal */
				0);	/* No offset */
	lcnt = i2c_fh_scl_lcnt(input_clock_khz,
				13,	/* tLOW = 1.3 us */
				3,	/* tf = 0.3 us */
				0);	/* No offset */
	I2c_SetFsHcnt(i2c_base, hcnt);
	I2c_SetFsLcnt(i2c_base, lcnt);

	__fh_i2c_set_bus_speed(i2c_base, speed);
}

static int fullhan_i2c_xfer(struct udevice *bus, struct i2c_msg *msg,
			       int nmsgs)
{
	struct fh_i2c *i2c = dev_get_priv(bus);
	int ret;

	debug("i2c_xfer: %d messages\n", nmsgs);
	for (; nmsgs > 0; nmsgs--, msg++) {
		debug("i2c_xfer: chip=0x%x, len=0x%x\n", msg->addr, msg->len);
		if (msg->flags & I2C_M_RD) {
			ret = __fh_i2c_read(i2c->base, msg->addr, 0, 0,
					    msg->buf, msg->len);
		} else {
			ret = __fh_i2c_write(i2c->base, msg->addr, 0, 0,
					     msg->buf, msg->len);
		}
		if (ret) {
			debug("i2c_write: error sending\n");
			return -EREMOTEIO;
		}
		I2c_DisEnable(i2c->base);
	}

	return 0;
}

static int fullhan_i2c_probe_chip(struct udevice *bus, uint chip_addr,
				     uint chip_flags)
{
	struct fh_i2c *i2c = dev_get_priv(bus);
	unsigned int i2c_base = i2c->base;
	u32 tmp;
	int ret;

	/* Try to read the first location of the chip */
	ret = __fh_i2c_read(i2c_base, chip_addr, 0, 1, (uchar *)&tmp, 1);
	if (ret)
		__fh_i2c_init(i2c_base, I2C_SPEED_FAST, 0);
	I2c_DisEnable(i2c_base);

	return ret;
}

static int fullhan_i2c_probe(struct udevice *bus)
{
	struct fh_i2c *priv = dev_get_priv(bus);
	struct fh_i2c_device *device = bus->platdata;

	priv->base = device->base;
	if (!priv->base) {
		printf("%s init failed", bus->name);
		return -1;
	}
	__fh_i2c_init(priv->base, I2C_SPEED_FAST, 0);
	I2c_SetIntrMask(priv->base, FH_IC_INTR_NONE);
	printf("%s init success\n", bus->name);

	return 0;
}

static int fullhan_i2c_bind(struct udevice *dev)
{
	char name[20];
	struct fh_i2c_device *device = dev->platdata;

	dev->req_seq = device->id;
	sprintf(name, "fh_i2c%u", device->id);
	device_set_name(dev, name);

	return 0;
}

static const struct dm_i2c_ops fullhan_i2c_ops = {
	.xfer		= fullhan_i2c_xfer,
	.probe_chip	= fullhan_i2c_probe_chip,
};

U_BOOT_DRIVER(FH_I2C) = {
	.name	= "FH_I2C",
	.id	= UCLASS_I2C,
	.bind	= fullhan_i2c_bind,
	.probe	= fullhan_i2c_probe,
	.priv_auto_alloc_size = sizeof(struct fh_i2c),
	.ops	= &fullhan_i2c_ops,
};